package rs.udd.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.document.Document;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import rs.udd.exceptions.UddException;
import rs.udd.model.Advert;
import rs.udd.model.Advertiser;
import rs.udd.model.Agent;
import rs.udd.model.Category;
import rs.udd.model.KeyWord;
import rs.udd.repository.AdvertRepository;
import rs.udd.repository.AdvertiserRepository;
import rs.udd.repository.AgentRepository;
import rs.udd.repository.CategoryRepository;
import rs.udd.repository.KeyWordRepository;
import rs.udd.service.document.indexer.IndexManager;
import rs.udd.service.document.indexer.handler.PDFHandler;
import rs.udd.service.document.model.RequiredHighlight;
import rs.udd.service.document.query.QueryBuilder;
import rs.udd.service.document.searcher.InformationRetriever;
import rs.udd.service.document.searcher.ResultRetriever;
import rs.udd.service.dto.search.PrepareSearchDto;
import rs.udd.service.dto.search.SearchAdvertDto;
import rs.udd.service.dto.search.SearchAdvertsResultDto;
import rs.udd.service.dto.search.SearchAgentCvDto;
import rs.udd.service.dto.search.SearchAgentCvResultDto;
import rs.udd.service.util.FileUtil.FileTypes;
import rs.udd.service.util.SecurityUtil;

@Service
@Transactional
public class SearchServiceImpl implements SearchService {

	private final Logger LOGGER = LogManager.getLogger(getClass());
	
	private static final Long NOT_SELECTED = -1L;

	@Resource
	private CategoryRepository categoryRepository;

	@Resource
	private AdvertiserRepository advertiserRepository;

	@Resource
	private KeyWordRepository keyWordRepository;
	
	@Resource
	private AdvertRepository advertRepository;
	
	@Resource
	private AgentRepository agentRepository;

	@Override
	public PrepareSearchDto prepareSearch() throws UddException {

		UserDetails loggedUser = null;
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();

		List<Category> categories;
		List<Advertiser> advertisers;

		if (auth instanceof AnonymousAuthenticationToken) { //anonymous
			LOGGER.info("Anonymous user searching...");
		} else {
			loggedUser = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		}

		if (loggedUser instanceof Agent) {
			Agent agent = (Agent) loggedUser;
			LOGGER.info("Agent '" + agent.getUsername() + "' searching...");
			categories = categoryRepository.findAllCategoriesShownByAgent(agent.getId());
			advertisers = advertiserRepository.findAllAdvertisersShownByAgent(agent);
		} else if (loggedUser instanceof Advertiser) {
			Advertiser advertiser = (Advertiser) loggedUser;
			LOGGER.info("Advertiser '" + advertiser.getUsername() + "' searching...");
			categories = categoryRepository.findAll();
			advertisers = Arrays.asList(advertiser);
		} else { // admin or anonymous
			categories = categoryRepository.findAll();
			advertisers = advertiserRepository.findAll();
		}

		PrepareSearchDto dto = new PrepareSearchDto();
		dto.setCategories(categories);
		dto.setAdvertisers(advertisers);

		return dto;
	}

	@Override
	public List<SearchAdvertsResultDto> performAdvertSearch(SearchAdvertDto dto, List<RequiredHighlight> suggestions) throws UddException {

		try {
			BooleanQuery bQuery = new BooleanQuery();
			
			RequiredHighlight titlerh = handleSearchTitle(dto, bQuery);
			RequiredHighlight authorrh = handleSearchAuthor(dto, bQuery);
			RequiredHighlight keywordrh = handleSearchKeyWords(dto, bQuery);
			RequiredHighlight abstractTextrh = handleSearchAbstractText(dto, bQuery);
			RequiredHighlight textrh = handleSearchText(dto, bQuery);
			RequiredHighlight categoryrh = handleSearchCategory(dto, bQuery);
	
			List<RequiredHighlight> rhs = new ArrayList<RequiredHighlight>();
			rhs.add(titlerh);
			rhs.add(authorrh);
			rhs.add(keywordrh);
			rhs.add(abstractTextrh);
			rhs.add(textrh);
			rhs.add(categoryrh);
			
			if (titlerh != null){
				List<RequiredHighlight> list = InformationRetriever.getSuggestions(titlerh.getTerm());
				for (RequiredHighlight rh : list) {
					String newValue = rh.getValue();
					dto.setTitle(newValue);
				}
				suggestions.addAll(list);
			}
			if (authorrh != null){
				List<RequiredHighlight> list = InformationRetriever.getSuggestions(authorrh.getTerm());
				for (RequiredHighlight rh : list) {
					String newValue = rh.getValue();
					Advertiser advertiser = advertiserRepository.findByUsername(newValue);
					if (advertiser != null) {
						dto.setAdvertiser(advertiser.getId());
					} else {
						dto.setAdvertiser(NOT_SELECTED);
					}
				}
				suggestions.addAll(list);
			}
			if (keywordrh != null){
				List<RequiredHighlight> list = InformationRetriever.getSuggestions(keywordrh.getTerm());
				for (RequiredHighlight rh : list) {
					String newValue = rh.getValue();
					dto.setKeyWords(newValue);
				}
				suggestions.addAll(list);
			}
			if (abstractTextrh != null){
				List<RequiredHighlight> list = InformationRetriever.getSuggestions(abstractTextrh.getTerm());
				for (RequiredHighlight rh : list) {
					String newValue = rh.getValue();
					dto.setAbstractText(newValue);
				}
				suggestions.addAll(list);
			}
			if (textrh != null){
				List<RequiredHighlight> list = InformationRetriever.getSuggestions(textrh.getTerm());
				for (RequiredHighlight rh : list) {
					String newValue = rh.getValue();
					dto.setAdvertContent(newValue);
				}
				suggestions.addAll(list);
			}
			if (categoryrh != null){
				List<RequiredHighlight> list = InformationRetriever.getSuggestions(categoryrh.getTerm());
				for (RequiredHighlight rh : list) {
					String newValue = rh.getValue();
					Category category = categoryRepository.findCategoryByName(newValue);
					if (category != null) {
						dto.setAdvertiser(category.getId());
					} else {
						dto.setAdvertiser(NOT_SELECTED);
					}
				}
				suggestions.addAll(list);
			}
			
			List<SearchAdvertsResultDto> rawResults = InformationRetriever.getData(bQuery, rhs);
			List<SearchAdvertsResultDto> results = filterAdvertResults(rawResults);
			return results;
		} catch(Exception e) {
			LOGGER.error("Exception", e);
			throw new UddException(e.getMessage());
		}
	}
	
	@Override
	public List<SearchAgentCvResultDto> performAgentSearch(SearchAgentCvDto dto) throws UddException {
		try {
			BooleanQuery bQuery = new BooleanQuery();
			
			handleAgentSearch(dto, bQuery);
	
			List<Document> documentResults = ResultRetriever.getResults(bQuery);
			List<SearchAgentCvResultDto> results = prepareAgentResults(documentResults);
			return results;
		} catch(Exception e) {
			LOGGER.error("Exception", e);
			throw new UddException(e.getMessage());
		}
	}

	@Override
	public List<SearchAdvertsResultDto> performMoreLikeThisSearch(String documentId) {
		Advert advert = advertRepository.findByIndexedDocumentId(documentId);
		String filePath = advert.getFilePath();
		List<SearchAdvertsResultDto> rawResults = InformationRetriever.getMoreLikeThis(filePath);
		List<SearchAdvertsResultDto> filteredResults = filterAdvertResults(rawResults);
		
		return filteredResults;
	}
	
	private List<SearchAdvertsResultDto> filterAdvertResults(List<SearchAdvertsResultDto> rawResults) {
		if (rawResults.isEmpty())
			return rawResults;
		
		Advertiser loggedAdvertiser = null;
		Agent loggedAgent = null;
		boolean advertiserLoggedIn = SecurityUtil.checkLoggedRole(Advertiser.class);
		boolean agentLoggedIn = SecurityUtil.checkLoggedRole(Agent.class);
		if (advertiserLoggedIn) {
			loggedAdvertiser = SecurityUtil.getLoggedUser(Advertiser.class);
		} else if (agentLoggedIn) {
			loggedAgent = SecurityUtil.getLoggedUser(Agent.class);
			loggedAgent = agentRepository.findOne(loggedAgent.getId());
		}
			
		List<SearchAdvertsResultDto> filteredResults = new ArrayList<SearchAdvertsResultDto>(rawResults.size());
		for (SearchAdvertsResultDto result : rawResults) {
			String documentId = result.getDocumentId();
			Document document = IndexManager.getIndexer().getDocumentById(result.getDocumentId());
			String[] documentTypes = document.getValues(PDFHandler.TYPE);
			boolean isAdvertType = PDFHandler.checkType(documentTypes, FileTypes.ADVERT);
			
			if (isAdvertType) {
				Advert advert = advertRepository.findByIndexedDocumentId(documentId);
				Long advertId = advert.getId();
				result.setAdvertId(advertId);
				
				if (loggedAdvertiser != null) {	// advertiser logged in?
					boolean checkOwner = advert.getAdvertiser().getId() == loggedAdvertiser.getId();
					if (checkOwner) {	// logged advertiser is owner of document?
						filteredResults.add(result);
					}
				} else if (loggedAgent != null) {
					boolean hiddenCategory = checkHiddenCategory(document, loggedAgent);
					boolean hiddenAdvertiser = checkHiddenAdvertiser(document, loggedAgent);
					if (!hiddenCategory && !hiddenAdvertiser)
						filteredResults.add(result);
				} else {	// if anonymous or admin are searching
					filteredResults.add(result);
				}
			}
		}
		
		return filteredResults;
	}
	
	private boolean checkHiddenCategory(Document doc, Agent loggedAgent) {
		if (doc == null)
			return false;
		
		String[] docCategories = doc.getValues(PDFHandler.CATEGORIES);
		boolean hiddenCategory = false;
		for (String categoryName : docCategories) {
			Category category = categoryRepository.findCategoryByName(categoryName.trim());
			hiddenCategory = loggedAgent.getHiddenCategories().contains(category);
			if (hiddenCategory)
				break;
		}
		return hiddenCategory;
	}

	private boolean checkHiddenAdvertiser(Document doc, Agent loggedAgent) {
		if (doc == null)
			return false;
			
		String docId = doc.get(PDFHandler.ID);
		Advert advert = advertRepository.findByIndexedDocumentId(docId);
		boolean hidden = loggedAgent.getHiddenAdvertisers().contains(advert.getAdvertiser());
		return hidden;
	}
	
	private List<SearchAgentCvResultDto> prepareAgentResults(List<Document> documentResults) {
		if (documentResults.isEmpty())
			return new ArrayList<SearchAgentCvResultDto>(0);
		
		List<SearchAgentCvResultDto> results = new ArrayList<SearchAgentCvResultDto>(documentResults.size());
		for (Document doc : documentResults) {
			String indexedDocumentId = doc.get(PDFHandler.ID);
			Document document = IndexManager.getIndexer().getDocumentById(indexedDocumentId);
			String[] documentTypes = document.getValues(PDFHandler.TYPE);
			boolean isCvType = PDFHandler.checkType(documentTypes, FileTypes.CV);
			
			Agent agent = agentRepository.findByIndexedDocumentId(indexedDocumentId);
			if (isCvType && agent != null) {
	
				SearchAgentCvResultDto resultDto = new SearchAgentCvResultDto();
				
				resultDto.setFirstName(agent.getFirstname());
				resultDto.setLastName(agent.getLastname());
				resultDto.setUsername(agent.getUsername());
				resultDto.setAgentId(agent.getId());
				
				results.add(resultDto);
			}
		}
		
		return results;
	}
	
	private RequiredHighlight handleSearchTitle(SearchAdvertDto dto, BooleanQuery bQuery) throws Exception {
		String title = dto.getTitle();
		if (StringUtils.isNotEmpty(title)) {
			Query query = QueryBuilder.buildQuery(dto.getTitleSearchType(), PDFHandler.TITLE, title);
			bQuery.add(query, dto.getTitleOccur());
			return new RequiredHighlight(PDFHandler.TITLE, title, dto);
		}
		return null;
	}
	
	private RequiredHighlight handleSearchAuthor(SearchAdvertDto dto, BooleanQuery bQuery) throws Exception {
		Long selectedAdvertiser = dto.getAdvertiser();
		if (selectedAdvertiser != NOT_SELECTED) {
			Advertiser advertiser = advertiserRepository.findOne(selectedAdvertiser);
			String author = advertiser.getUsername();
			Query query = QueryBuilder.buildQuery(dto.getAdvertiserSearchType(), PDFHandler.AUTHOR, author);
			bQuery.add(query, dto.getAdvertiserOccur());
			return new RequiredHighlight(PDFHandler.AUTHOR, author, dto);
		}
		return null;
	}
	
	private RequiredHighlight handleSearchKeyWords(SearchAdvertDto dto, BooleanQuery bQuery) throws Exception {
		String[] keyWordsArray = StringUtils.split(dto.getKeyWords(), ",");
		List<String> stringKeyWords = Arrays.asList(keyWordsArray);
		List<KeyWord> keyWords = new ArrayList<>();
		for (String keyWordString : stringKeyWords) {
			KeyWord keyWord = keyWordRepository.findByWord(keyWordString);
			if (keyWord == null) {
				keyWord = keyWordRepository.save(keyWord);
			}
			keyWords.add(keyWord);
		}
		
		if (!keyWords.isEmpty()) {
			String keywords = "";
			for (KeyWord keyWord : keyWords) {
				Query query = QueryBuilder.buildQuery(dto.getKeyWordsSearchType(), PDFHandler.KEY_WORD, keyWord.getWord());
				bQuery.add(query, dto.getKeyWordsOccur());
				keywords += keyWord.getWord() + " ";
			}
			return new RequiredHighlight(PDFHandler.AUTHOR, keywords.trim(), dto);
		}
		return null;
	}

	private RequiredHighlight handleSearchAbstractText(SearchAdvertDto dto, BooleanQuery bQuery) throws Exception {
		String abstractText = dto.getAbstractText();
		if (StringUtils.isNotEmpty(abstractText)) {
			Query query = QueryBuilder.buildQuery(dto.getAbstractTextSearchType(), PDFHandler.ABSTRACT_TEXT, abstractText);
			bQuery.add(query, dto.getAbstractTextOccur());
			return new RequiredHighlight(PDFHandler.ABSTRACT_TEXT, abstractText, dto);
		}
		return null;
	}

	private RequiredHighlight handleSearchText(SearchAdvertDto dto, BooleanQuery bQuery) throws Exception {
		String text = dto.getAdvertContent();
		if (StringUtils.isNotEmpty(text)) {
			Query query = QueryBuilder.buildQuery(dto.getAdvertContentSearchType(), PDFHandler.TEXT, text);
			bQuery.add(query, dto.getAdvertContentOccur());
			return new RequiredHighlight(PDFHandler.TEXT, text, dto);
		}
		return null;
	}

	private RequiredHighlight handleSearchCategory(SearchAdvertDto dto, BooleanQuery bQuery) throws Exception {
		Long selectedCategory = dto.getCategory();
		if (selectedCategory != NOT_SELECTED) {
			Category category = categoryRepository.findOne(selectedCategory);
			String categoryName = category.getName();
			Query query = QueryBuilder.buildQuery(dto.getCategorySearchType(), PDFHandler.CATEGORIES, categoryName);
			bQuery.add(query, dto.getCategoryOccur());
			return new RequiredHighlight(PDFHandler.CATEGORIES, categoryName, dto);
		}
		return null;
	}
	
	private void handleAgentSearch(SearchAgentCvDto dto, BooleanQuery bQuery) throws Exception {
		String text = dto.getCvText();
		if (StringUtils.isNotEmpty(text)) {
			Query query = QueryBuilder.buildQuery(dto.getSearchType(), PDFHandler.TEXT, text);
			bQuery.add(query, dto.getOccur());
		}
	}

}
